iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0

▍程式碼

<!-- quiz_report.html -->

<head>
    <title>測驗成績統計與報表</title>
    <!-- 導入樣式、圖表資源 -->
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>

</head>
<body>
    <div>
        <h1>📊 測驗成績分析報告</h1>

        <div id="summary-section">
            <h2>總成績概覽</h2>
            <div>
                <div>
                    <p>總題數</p>
                    <p id="total-questions">0</p>
                </div>
                <div>
                    <p>答對題數 / 總分</p>
                    <p id="correct-count">0 / 0</p>
                </div>
                <div>
                    <p>總正確率</p>
                    <p id="overall-accuracy">0%</p>
                </div>
            </div>
        </div>

        <!-- 圖表 -->
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
            <!-- 圓餅圖:題目分佈 -->
            <div class="lg:col-span-1 container-card bg-white p-6 rounded-xl flex flex-col items-center">
                <h2>章節(來源書籍)題目分佈</h2>
                <div class="w-full h-80">
                    <canvas id="topicDistributionChart"></canvas>
                </div>
            </div>

            <!-- 長條圖:章節正確率 -->
            <div class="lg:col-span-2 container-card bg-white p-6 rounded-xl">
                <h2>弱點章節分析 (正確率長條圖)</h2>
                <div class="w-full h-96">
                    <canvas id="accuracyBarChart"></canvas>
                </div>
            </div>
        </div>

        <!-- 表格 -->
        <div>
            <h2>📚 詳細章節成績</h2>
            <div>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">來源書籍 (章節)</th>
                            <th scope="col">總題數</th>
                            <th scope="col">答對題數</th>
                            <th scope="col">正確率</th>
                            <th scope="col">弱點指標</th>
                        </tr>
                    </thead>
                    <tbody id="accuracy-table-body">
                        <!-- 錯題資訊 -->
                    </tbody>
                </table>
            </div>
        </div>

    </div>

    <script>
        // 後端接收數據
        const quizResults = {{ quiz_results | tojson | safe }};
        const SCORE_PER_QUESTION = 2;

        // 圖表顏色配置 
        const ACCURACY_COLORS = [...];

        // 處理測驗結果,計算成績、正確率
        function analyzeResults(results) {
            let totalQuestions = results.length;
            let correctCount = 0;
            const bookAccuracy = {};

            results.forEach(item => {
                // 處理來源書籍,如果為空則設為無
                const book = item.book_source || '無';
                const isCorrect = item.is_correct;

                if (isCorrect) {
                    correctCount++;
                }

                // 更新章節數據
                if (!bookAccuracy[book]) {
                    bookAccuracy[book] = { total: 0, correct: 0, accuracy: 0, color: '' };
                }

                bookAccuracy[book].total++;
                if (isCorrect) {
                    bookAccuracy[book].correct++;
                }
            });

            // 計算各章節準確率
            const sortedBooks = Object.keys(bookAccuracy).sort();
            sortedBooks.forEach((book, index) => {
                const data = bookAccuracy[book];
                data.accuracy = data.total > 0 ? (data.correct / data.total) * 100 : 0;
                data.color = ACCURACY_COLORS[index % ACCURACY_COLORS.length];
            });

            return {
                totalQuestions,
                correctCount,
                bookAccuracy,
            };
        }

        // 渲染成績概覽
        function renderSummary(summary) {
            const { totalQuestions, correctCount } = summary;
            const totalScore = correctCount * SCORE_PER_QUESTION;
            const overallAccuracy = totalQuestions > 0 ? (correctCount / totalQuestions * 100).toFixed(1) : 0;

            document.getElementById('total-questions').textContent = totalQuestions;
            document.getElementById('correct-count').textContent = `${correctCount} / ${totalScore}`;
            document.getElementById('overall-accuracy').textContent = `${overallAccuracy}%`;
        }

        // 渲染表格
        function renderTable(bookAccuracy) {
            const tbody = document.getElementById('accuracy-table-body');
            tbody.innerHTML = '';

            // 章節按正確率排列
            const sortedEntries = Object.entries(bookAccuracy)
                .sort(([, a], [, b]) => a.accuracy - b.accuracy);

            sortedEntries.forEach(([book, data]) => {
                const row = tbody.insertRow();
                row.className = 'hover:bg-gray-100';

                // 弱點標記 → 題目數 > 1 & 正確率低於 60% 
                const isWeakSpot = data.accuracy < 60 && data.total > 1;

                const indicatorClass = isWeakSpot ? 'bg-red-500 text-white font-semibold rounded-full p-1 text-xs text-center' : 'bg-green-500 text-white rounded-full p-1 text-xs text-center';
                const indicatorText = isWeakSpot ? '⚠️ 需加強' : '✅ 掌握';
                row.insertCell().textContent = book;
                row.insertCell().textContent = data.total;
                row.insertCell().textContent = data.correct;
                row.insertCell().textContent = `${data.accuracy.toFixed(1)}%`;
                const indicatorCell = row.insertCell();
                indicatorCell.innerHTML = `<span class="${indicatorClass}" style="min-width: 80px; display: inline-block;">${indicatorText}</span>`;
            });
        }

        // 避免重複渲染
        let distributionChartInstance = null;
        let accuracyChartInstance = null;

        // 渲染圓餅圖
        function renderDistributionChart(bookAccuracy) {
            if (distributionChartInstance) {
                distributionChartInstance.destroy();
            }

            const labels = Object.keys(bookAccuracy);
            const data = labels.map(book => bookAccuracy[book].total);
            const colors = labels.map(book => bookAccuracy[book].color);

            distributionChartInstance = new Chart(document.getElementById('topicDistributionChart'), {
                type: 'pie',
                data: {
                    labels: labels,
                    datasets: [{
                        label: '題目數量',
                        data: data,
                        backgroundColor: colors,
                        hoverOffset: 8
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: {
                            position: 'right',
                        },
                        title: {
                            display: false
                        }
                    }
                }
            });
        }

        // 渲染長條圖
        function renderAccuracyChart(bookAccuracy) {
            if (accuracyChartInstance) {
                accuracyChartInstance.destroy();
            }

            // 按正確率排列
            const sortedEntries = Object.entries(bookAccuracy)
                .sort(([, a], [, b]) => a.accuracy - b.accuracy);

            const labels = sortedEntries.map(([book]) => book);
            const data = sortedEntries.map(([, data]) => parseFloat(data.accuracy.toFixed(1)));
            const colors = sortedEntries.map(([, data]) => data.color);

            accuracyChartInstance = new Chart(document.getElementById('accuracyBarChart'), {
                type: 'bar',
                data: {
                    labels: labels,
                    datasets: [{
                        label: '正確率 (%)',
                        data: data,
                        backgroundColor: colors,
                        borderColor: colors,
                        borderWidth: 1,
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    indexAxis: 'y', // 水平長條圖
                    scales: {
                        x: {
                            beginAtZero: true,
                            max: 100,
                            title: {
                                display: true,
                                text: '正確率 (%)'
                            }
                        }
                    },
                    plugins: {
                        legend: {
                            display: false,
                        },
                        title: {
                            display: true,
                            text: '章節正確率 (分數越低越需優先加強)'
                        }
                    }
                }
            });
        }


        window.onload = function() {
            if (!quizResults || quizResults.length === 0) {
                document.getElementById('summary-section').innerHTML = '<p class="text-center text-xl text-red-500">錯誤:找不到測驗結果數據可供分析。請先完成測驗。</p>';
                return;
            }

            const summary = analyzeResults(quizResults);
            renderSummary(summary);
            renderTable(summary.bookAccuracy);
            renderDistributionChart(summary.bookAccuracy);
            renderAccuracyChart(summary.bookAccuracy);
        };

    </script>
</body>

# views.py

@app.route('/report_page')
def report_page():
    report_data = TEMP_QUIZ_RESULTS.get('latest_report')
    
    if not report_data:
        return redirect(url_for('quiz'))
        
    return render_template('quiz_report.html', quiz_results=report_data['detailed_results'])

上一篇
DAY18 - 題目收藏
下一篇
DAY20 - 錯題重練模式
系列文
打造你的數位圖書館:從雜亂檔案到個人化知識庫26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言